Etiqueta: middleware

  • Capas invisibles de seguridad en PHP que bloquean ataques

    Capas invisibles de seguridad en PHP que bloquean ataques

    Resumen: las “capas invisibles” son defensas integradas en el runtime o en la capa infraestrutural que bloquean clases completas de ataques antes de que lleguen al negocio. Este artículo describe seis capas prácticas y muestra ejemplos de implementación en PHP.

    Introducción

    Los atacantes buscan huecos repetibles y fáciles: salidas sin escapar, sesiones que no cambian, consultas sin parámetros. Las capas invisibles obligan al runtime o a la infraestructura a aplicar defensas por defecto, reduciendo la carga mental del desarrollador y cerrando vectores comunes.

    Prerrequisitos

    Antes de integrar estas capas conviene contar con un entorno que permita soluciones como hashing moderno, plantillas con auto-escaping y mecanismos de almacenamiento para límites por IP.

    • PHP con soporte para Argon2id (mencionado en el texto base).
    • Motor de plantillas con auto-escaping (por ejemplo, Twig o Blade, o equivalente).
    • Acceso a la base de datos vía ORM o biblioteca que permita prepared statements.
    • Sistema de caché o store (Redis, memcached, etc.) para rate-limiting y rotación de tokens.

    Desarrollo

    Procedimiento

    Capa 1 — Auto-escaping a nivel de motor de plantillas: configure el motor para escapar por defecto las salidas. Así, incluso si un desarrollador olvida sanitizar una variable, el motor evita XSS a la salida.

    Capa 2 — Hashing memory-hard para credenciales: use algoritmos que consuman memoria (Argon2id o scrypt). Según el texto base, Argon2id es la opción recomendada para evitar que dumps de contraseñas sean fáciles de crackear.

    Capa 3 — Rotación automática de tokens: regenere identificadores de sesión en eventos críticos (login, elevación de privilegios) y mantenga un tiempo de validez corto para tokens expuestos.

    Capa 4 — Firewall de consultas dentro del ORM: haga que el acceso a la BD use solo consultas parametrizadas. Si el ORM obliga a parámetros, las inyecciones se eliminan en la práctica.

    Capa 5 — Middleware adaptativo a la tasa (rate-adaptive): aumente la latencia o limite intentos según patrones sospechosos. El objetivo es hacer el brute force económicamente inviable sin afectar a usuarios legítimos.

    Capa 6 — Content Security Policy (CSP): entregue cabeceras CSP estrictas para limitar fuentes de scripts, iframes y orígenes. Esto reduce el impacto de XSS y evita exfiltración desde el navegador.

    Ejemplos

    A continuación hay ejemplos concisos para poner en práctica las capas descritas.

    <?php
    // Hashing con Argon2id
    $password = 'usuario-password';
    $options = ['memory_cost' => 1<<17, 'time_cost' => 4, 'threads' => 2];
    $hash = password_hash($password, PASSWORD_ARGON2ID, $options);
    Lenguaje del código: PHP (php)

    Ejemplo de consultas parametrizadas con PDO (ORMs modernos aplican esto por defecto).

    <?php
    $stmt = $pdo->prepare('SELECT id, email FROM users WHERE email = :email');
    $stmt->execute([':email' => $email]);
    $user = $stmt->fetch();
    Lenguaje del código: PHP (php)

    Rotación de token/ID de sesión en eventos clave.

    <?php
    session_start();
    // Tras login o cambio de privilegios
    session_regenerate_id(true);
    $_SESSION['rotated_at'] = time();
    // Verificar caducidad en cada petición
    if (isset($_SESSION['rotated_at']) && time() - $_SESSION['rotated_at'] > 3600) {
        // forzar logout o revalidación
    }
    Lenguaje del código: PHP (php)

    Cabecera CSP mínima para reducir ejecución de scripts externos.

    <?php
    header("Content-Security-Policy: default-src 'self'; script-src 'self'");
    Lenguaje del código: PHP (php)

    Middleware simple para introducir fricción adaptativa en intentos de login.

    <?php
    $ip = $_SERVER['REMOTE_ADDR'];
    $attempts = $cache->get("login:{$ip}") ?? 0;
    if ($attempts > 5) {
        // añadir demora creciente (ej. usleep) para frustrar ataques automatizados
        usleep(min(500000 * ($attempts - 5), 2000000));
    }
    $cache->set("login:{$ip}", $attempts + 1, 300);
    Lenguaje del código: PHP (php)

    Checklist

    1. Habilitar auto-escaping en el motor de plantillas.
    2. Usar hashing memory-hard (Argon2id) para contraseñas.
    3. Forzar consultas parametrizadas desde el ORM o capa de datos.
    4. Implementar rotación de tokens y control de caducidad.
    5. Agregar middleware rate-adaptive para endpoints sensibles.
    6. Entregar cabeceras CSP estrictas y revisar políticas periódicamente.

    Conclusión

    Las capas invisibles eliminan vectores enteros de ataque al aplicar buenas prácticas desde el runtime y la infraestructura. Implementarlas reduce la superficie de ataque y permite a los equipos centrarse en la lógica de negocio con mayor confianza.

  • Dejar que PHP narre su runtime: guía práctica

    Dejar que PHP narre su runtime: guía práctica

    \n

    Resumen: este artículo muestra cómo aplicar un patrón de \”Narrator\” en PHP para que la propia aplicación narre su flujo de ejecución. Incluye un diseño básico, integración y ejemplos de código listos para adaptar.

    \n\n\n\n\n\n\n\n

    Introducción

    \n\n\n\n

    Las soluciones tradicionales de observabilidad entregan señales reactivas: métricas, trazas y logs que requieren correlación externa. En lugar de depender exclusivamente de herramientas externas, podemos hacer que la aplicación misma produzca una narración del runtime: eventos con contexto e intención unidos en hilos causales.

    \n\n\n\n

    Este enfoque complementa (no necesariamente reemplaza) la observabilidad convencional: su objetivo es mejorar la comprensibilidad interna —por ejemplo, por qué se tomó una decisión— y facilitar la depuración rápida sin saltar entre múltiples herramientas.

    \n\n\n\n

    Prerrequisitos

    \n\n\n\n
    • Entorno PHP donde puedas anexar objetos al ciclo de vida de la petición (middleware o front controller).
    • Un logger compatible PSR-3 (por ejemplo Monolog) o un adaptador propio para exportar hilos de eventos cuando sea necesario.
    • Mínima estructura para identificar contexto: request id, session id o user id para correlación.
    \n\n\n\n

    Desarrollo

    \n\n\n\n

    Procedimiento

    \n\n\n\n
    1. Diseñar una clase Narrator que viaje con la petición y acumule eventos con metadatos (tipo, intención, contexto, timestamp).
    2. Instrumentar puntos clave: routing, validaciones, intentos de reintento, decisiones que afecten el flujo.
    3. Decidir retención: caducar segmentos de memoria cuando ya no aporten valor, o agrupar eventos en hilos causales.
    4. Exportar o exponer los hilos en un formato que un visor timeline pueda consumir (JSON, evento estructurado a logger, API interna).
    \n\n\n\n

    A continuación se muestra una implementación mínima de una clase Narrator que acumula eventos y permite serializarlos. Es un punto de partida para adaptar a frameworks o middlewares.

    \n\n\n\n
    <?php\nnamespace App\Observability;\n\nclass Narrator\n{\n    private array $threads = [];\n    private array $currentContext = [];\n\n    public function startThread(string $id, array $meta = []): void\n    {\n        $this->threads[$id] = [\n            'id' => $id,\n            'meta' => $meta,\n            'events' => [],\n        ];\n    }\n\n    public function annotate(string $threadId, string $type, string $message, array $context = []): void\n    {\n        $this->threads[$threadId]['events'][] = [\n            'ts' => microtime(true),\n            'type' => $type,\n            'message' => $message,\n            'context' => $context,\n        ];\n    }\n\n    public function setContext(array $context): void\n    {\n        $this->currentContext = $context;\n    }\n\n    public function expireThread(string $threadId): void\n    {\n        unset($this->threads[$threadId]);\n    }\n\n    public function export(): array\n    {\n        return $this->threads;\n    }\n}\n
    \n\n\n\n

    Integrar el Narrator con el flujo de la petición (por ejemplo como middleware) permite que cada controlador o servicio registre decisiones y motivos, y al final de la petición exporte el hilo a un logger o lo envíe a un endpoint interno.

    \n\n\n\n
    <?php\n// Ejemplo simplificado de middleware (framework-agnóstico)\nuse App\\Observability\\Narrator;\nuse Psr\\Log\\LoggerInterface;\n\nclass NarratorMiddleware\n{\n    private Narrator $narrator;\n    private LoggerInterface $logger;\n\n    public function __construct(Narrator $narrator, LoggerInterface $logger)\n    {\n        $this->narrator = $narrator;\n        $this->logger = $logger;\n    }\n\n    public function handle($request, $next)\n    {\n        $requestId = $request->getAttribute('request_id') ?? uniqid('req_', true);\n        $this->narrator->startThread($requestId, ['path' => $request->getUri()]);\n        $this->narrator->setContext(['request_id' => $requestId]);\n\n        $response = $next($request);\n\n        // Al final de la petición exportamos el hilo\n        $threads = $this->narrator->export();\n        $this->logger->info('narrator.threads', ['threads' => $threads]);\n\n        return $response;\n    }\n}\n
    \n\n\n\n

    En el ejemplo anterior, el logger recibe la estructura completa; un visor de timeline o un consumidor puede mostrar los eventos como una historia por petición. También puede enviarse a un índice o guardarse en almacenamiento temporal.

    \n\n\n\n
    {\n  \"req_606e2a\": {\n    \"id\": \"req_606e2a\",\n    \"meta\": {\"path\": \"/checkout\"},\n    \"events\": [\n      {\"ts\": 1690000000.123, \"type\": \"decision\", \"message\": \"cookie override - route B\", \"context\": {\"cookie\": \"promo\"}},\n      {\"ts\": 1690000000.456, \"type\": \"intent\", \"message\": \"calculate delivery estimate\", \"context\": {\"address\": \"...\"}},\n      {\"ts\": 1690000000.789, \"type\": \"notice\", \"message\": \"missing delivery estimate - user exited\", \"context\": {}}\n    ]\n  }\n}\n
    \n\n\n\n

    Ejemplos

    \n\n\n\n

    Ejemplo de uso dentro de una función de negocio: anotar la intención antes de ejecutar y el resultado después. Así se preserva la causalidad entre intención y efecto.

    \n\n\n\n
    <?php\n// Dentro de un servicio\nfunction applyCoupon(Narrator $narrator, string $threadId, array $couponData)\n{\n    $narrator->annotate($threadId, 'intent', 'apply coupon', ['coupon' => $couponData['code']]);\n\n    // Lógica real\n    $applied = false;\n    // ... comprobar validaciones, límites, fecha ...\n\n    if ($applied) {\n        $narrator->annotate($threadId, 'result', 'coupon applied', ['discount' => 10]);\n    } else {\n        $narrator->annotate($threadId, 'result', 'coupon rejected', ['reason' => 'expired']);\n    }\n\n    return $applied;\n}\n
    \n\n\n\n

    Con esta información, los equipos de producto y soporte pueden leer la cadena de eventos y comprender qué decisión tomó la aplicación y por qué, sin reconstruir el estado a partir de múltiples fuentes.

    \n\n\n\n

    Checklist

    \n\n\n\n
    1. Decidir el alcance de narración: qué tipos de eventos y qué contexto incluir.
    2. Agregar un objeto Narrator accesible en el ciclo de vida de la petición.
    3. Instrumentar puntos críticos: routing, validaciones, retries, fallos y decisiones de negocio.
    4. Definir política de expiración o agregación para evitar ruido innecesario.
    5. Elegir destino de exportación: logger estructurado, índice temporal o UI de timeline.
    6. Validar que los eventos incluyan identificadores para correlación (request id, session id).
    \n\n\n\n

    Conclusión

    \n\n\n\n

    Hacer que PHP narre su propio runtime reduce la necesidad de interpretar trazas desconectadas y acelera la resolución de problemas. Empieza por una implementación pequeña: una clase Narrator, puntos de instrumentación selectos y un canal para exportar hilos.

    \n\n\n\n

    Este patrón no elimina herramientas de observabilidad, pero aporta una capa de contexto y causalidad que hace a las aplicaciones más autoexplicativas y a los equipos menos dependientes de correlaciones externas.

    \n\n
  • AsyncRequestContext en Laravel: reducir middleware con contexto

    AsyncRequestContext en Laravel: reducir middleware con contexto

    Este artículo explica cómo reemplazar middleware que solo propagan datos por petición con un contexto scoped (AsyncRequestContext) en Laravel. El objetivo: reducir la complejidad de rutas, mejorar la claridad de dependencias y facilitar pruebas unitarias.

    Incluye el diseño del contexto, el enlace en el service container y ejemplos de uso en acciones, controladores, rutas y pruebas.

    Introducción

    El patrón AsyncRequestContext propone un contenedor ligero por petición para almacenar estado que actualmente se inyecta en Illuminate\Http\Request mediante middleware.

    En lugar de ensuciar el Request con claves arbitrarias, se inicializa un contexto por ciclo de petición y se usa desde acciones y bindings del contenedor.

    Prerrequisitos

    Conocimientos básicos de Laravel: service container, providers, uso de acciones/servicios y rutas. No se requiere configuración adicional en el framework más allá de registrar el provider.

    Desarrollo

    La idea central es exponer un RequestContext scoped que se inicializa al enlazar en el container y se limpia en termination. Evita ‘request pollution’ y hace explícitas las dependencias.

    <?php
    namespace App\Context;
    
    class RequestContext
    {
        private static ?array $data = null;
    
        public static function initialize(): void
        {
            self::$data = [];
        }
    
        public static function set(string $key, mixed $value): void
        {
            self::$data[$key] = $value;
        }
    
        public static function get(string $key): mixed
        {
            return self::$data[$key] ?? null;
        }
    
        public static function flush(): void
        {
            self::$data = null;
        }
    }
    Lenguaje del código: PHP (php)

    En el service provider se enlaza RequestContext para inicializarlo por petición y se registra un hook de terminating para limpiar el estado.

    <?php
    namespace App\Providers;
    
    use Illuminate\Support\ServiceProvider;
    use App\Context\RequestContext;
    
    class ContextServiceProvider extends ServiceProvider
    {
        public function boot()
        {
            $this->app->bind(RequestContext::class, function () {
                RequestContext::initialize();
                return new RequestContext();
            });
    
            $this->app->terminating(function () {
                RequestContext::flush();
            });
        }
    }
    Lenguaje del código: PHP (php)

    Procedimiento

    En lugar de usar middleware que sólo añade datos al Request, crea acciones que reciben el Request y escriben en el RequestContext. Eso permite invocarlas donde y cuando hagan falta.

    <?php
    namespace App\Actions;
    
    use Illuminate\Http\Request;
    use App\Context\RequestContext;
    use App\Models\Tenant;
    
    class ResolveTenant
    {
        public function __construct(private RequestContext $context) {}
    
        public function execute(Request $request): void
        {
            $tenant = Tenant::fromDomain($request->host());
            $this->context->set('tenant', $tenant);
        }
    }
    Lenguaje del código: PHP (php)

    Consume el contexto explícitamente en controladores o servicios, lo que revela dependencias y facilita pruebas unitarias.

    <?php
    namespace App\Http\Controllers;
    
    use App\Context\RequestContext;
    
    class ReportController
    {
        public function index(RequestContext $context)
        {
            $tenant = $context->get('tenant');
            return $tenant->reports()->paginate();
        }
    }
    Lenguaje del código: PHP (php)

    Las rutas pueden simplificarse ejecutando acciones de resolución dentro de la closure y pasando el contexto al controlador.

    <?php
    use App\Actions\ResolveTenant;
    use App\Context\RequestContext;
    use App\Http\Controllers\ReportController;
    use Illuminate\Support\Facades\Route;
    
    Route::get('/reports', function (
        ResolveTenant $resolve,
        RequestContext $context
    ) {
        $resolve->execute(request());
        return (new ReportController)->index($context);
    })->middleware('auth:api');
    Lenguaje del código: PHP (php)

    Con este enfoque mantienes middleware para filtrado (autenticación, throttling) y usas acciones/contexto para propagar estado.

    Ejemplos

    Ejemplo de binding contextual que elige una implementación según un valor en RequestContext.

    <?php
    // En AppServiceProvider.php
    $this->app->bind(ReportExporter::class, function () {
        $format = RequestContext::get('export_format') ?? 'csv';
        return match ($format) {
            'excel' => new ExcelExporter,
            'pdf'   => new PDFExporter,
            default => new CSVExporter,
        };
    });
    Lenguaje del código: PHP (php)

    Prueba unitaria sin levantar el HTTP layer inicializando el contexto manualmente:

    <?php
    test('exports tenant reports in excel', function () {
        RequestContext::initialize();
        RequestContext::set('tenant', Tenant::factory()->create());
        RequestContext::set('export_format', 'excel');
    
        $exporter = app(ReportExporter::class);
        $this->assertInstanceOf(ExcelExporter::class, $exporter);
    });
    Lenguaje del código: PHP (php)

    Checklist

    1. Auditar middleware que solo escriben datos en el Request.
    2. Refactorizar esas responsabilidades en acciones (ResolveX classes).
    3. Sustituir $request->get(‘foo’) por $context->get(‘foo’) donde corresponda.
    4. Reducir arrays de middleware en rutas dejando filtros reales (auth, throttle).
    5. Asegurar que RequestContext::flush() se ejecuta en terminating.

    Conclusión

    AsyncRequestContext ofrece una alternativa práctica al uso excesivo de middleware para propagar estado por petición. Mejora la claridad, facilita pruebas y mantiene middleware para filtrado.

    Middleware es para filtrado, no para propagar datos. Deja que el contexto lleve tu estado cross‑cutting.